﻿using System;
using System.Collections.Generic;
using DocumentFormat.OpenXml.Wordprocessing;
using OxmlDrawingWp = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using OxmlDrawing = DocumentFormat.OpenXml.Drawing;
using OxmlDrawingPics = DocumentFormat.OpenXml.Drawing.Pictures;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using TUM.Mustertexte_Parser;
using System.IO;

namespace Tum.CollabXT.DocxGen
{
    /// <summary>
    /// Class with necessary functionalities for setting paragraph texts.
    /// </summary>
    class DocumentUtils
    {
        public const string NamespaceUrl = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
        public const string NamespacePrefix = "w";
        public const string TagAttributeName = "petTag";

        public const string FallbackTableStyleName = "PETtable";
        public const string FallbackNumberingDefinitionPartName = "PETnumbering";

        private static readonly Random Rnd = new Random();

        #region Properties
        /// <summary>
        /// Main document part.
        /// </summary>
        public MainDocumentPart MainDocPart
        {
            get;
            private set;
        }
        private List<OpenXmlElement> RootElements = new List<OpenXmlElement>();

        /// <summary>
        /// Page width available for content.
        /// </summary>
        public long PageContentWidth
        {
            get
            {
                if (!_PageContentWidth.HasValue)
                {
                    var sectionPropertiesEnum = MainDocPart.Document.Body.Elements<SectionProperties>();
                    foreach (var sectionProperties in sectionPropertiesEnum)
                    {
                        var pageSizeEnum = sectionProperties.Elements<PageSize>();
                        var pageMarginEnum = sectionProperties.Elements<PageMargin>();

                        PageSize pageSize = null;
                        PageMargin pageMargin = null;
                        foreach (var p in pageSizeEnum)
                            pageSize = p;
                        foreach (var p in pageMarginEnum)
                            pageMargin = p;

                        if (pageSize != null && pageMargin != null)
                            _PageContentWidth = (long)((pageSize.Width - pageMargin.Left - pageMargin.Right) * 635);
                    }
                }
                return _PageContentWidth.Value;
            }
        }
        long? _PageContentWidth = null;

        /// <summary>
        /// Instance of TemplateConfig to be used for this DocumentUtils instance.
        /// </summary>
        public TemplateConfig TemplateDocumentConfig
        {
            get
            {
                if(_TemplateDocumentConfig != null)
                    return _TemplateDocumentConfig;
                else
                    return new TemplateConfig();
            }
            set
            {
                _TemplateDocumentConfig = value;
            }
        }
        TemplateConfig _TemplateDocumentConfig;
        #endregion

        #region Constructor
        public DocumentUtils(MainDocumentPart mainDocument)
        {
            if (mainDocument == null) throw new ArgumentNullException("mainDocument");
            MainDocPart = mainDocument;
            CreateNumberingPart();

            RootElements.Add(mainDocument.Document);
            foreach (HeaderPart curHeaderPart in mainDocument.HeaderParts)
                RootElements.Add(curHeaderPart.Header);
        }

        public DocumentUtils(MainDocumentPart mainDocumentPart, OpenXmlElement[] rootElements)
        {
            if (mainDocumentPart == null) throw new ArgumentNullException("mainDocumentPart");
            if (rootElements == null) throw new ArgumentNullException("rootElements");

            MainDocPart = mainDocumentPart;
            CreateNumberingPart();

            RootElements.AddRange(rootElements);
        }

        private void CreateNumberingPart()
        {
            if (!MainDocPart.GetPartsOfType<NumberingDefinitionsPart>().GetEnumerator().MoveNext())
            {
                //Part does not exist yet. Create it.
                MainDocPart.AddNewPart<NumberingDefinitionsPart>(FallbackNumberingDefinitionPartName);
            }
        }

        #endregion


        private static void GetElementsByTag(OpenXmlElement root, string tag, ref List<DocumentFormat.OpenXml.Wordprocessing.Text> foundElementsList)
        {
            foreach (OpenXmlElement openXmlElement in root.ChildElements)
            {
                if (openXmlElement is Text)
                {
                    foreach (OpenXmlAttribute curAttribute in openXmlElement.ExtendedAttributes)
                    {
                        if (curAttribute.LocalName.Equals(TagAttributeName) && curAttribute.Value.Equals(tag))
                        {
                            foundElementsList.Add((Text)openXmlElement);
                            break;
                        }
                    }
                }

                //Depth-first search recursion
                GetElementsByTag(openXmlElement, tag, ref foundElementsList);
            }
        }

        /// <summary>
        /// Sets the value of all elements, having a specific tag.
        /// </summary>
        /// <param name="elementTag">The ID.</param>
        /// <param name="text">The text.</param>
        public bool SetElementValue(string elementTag, string text)
        {
            var elementsList = new List<Text>();

            //Find elements
            foreach (OpenXmlElement curRoot in RootElements)
                GetElementsByTag(curRoot, elementTag, ref elementsList);

            //Set text
            foreach (Text curElement in elementsList)
                curElement.Text = text;
            return elementsList.Count > 0;
        }

        public bool SetElementValue(string elementTag, TextTemplate textTemplate)
        {
            var elementsList = new List<Text>();

            //Find elements
            foreach (OpenXmlElement curRoot in RootElements)
                GetElementsByTag(curRoot, elementTag, ref elementsList);

            //Set text
            foreach (Text curElement in elementsList)
            {
                try
                {
                    foreach (OpenXmlElement openXmlElement in ParseTemplateText(textTemplate.ParsedText, null))
                        curElement.InsertBeforeSelf(openXmlElement);
                }
                catch (Exception)
                {
                    
                    throw;
                }
                

                curElement.Remove();
            }
            return elementsList.Count > 0;
        }

        private IEnumerable<OpenXmlElement> ParseTemplateText(TemplateElement curTemplateElement, OpenXmlElement parentElement)
        {
            //Create element
            OpenXmlElement returnElement = null; //Element that is created in the curTemplateElement
            OpenXmlElement[] returnElements = null; //Not null, if multiple elements are to be returned
            OpenXmlElement addChildElement = null; //Element, to which sub-elements will be appended (returnElement, if null)
            bool noParagraphChildren = false;

            int curListId = 0;

            if(curTemplateElement is TemplateText)
            {
                if (((TemplateText)curTemplateElement).Text.Length == 0)
                    return new OpenXmlElement[0];
                var textElement = new Text(((TemplateText) curTemplateElement).Text) {Space = "preserve"};
                returnElement = new Run(new[] {textElement});
                goto addChildren;
            }
            if(curTemplateElement is TemplateParagraph)
            {
                if (!(parentElement is Paragraph))
                    returnElement = new Paragraph();

                noParagraphChildren = true;
                goto addChildren;
            }

            if (curTemplateElement is TemplateTable)
            {
                string tableStyleName = TemplateDocumentConfig.TableStyle;
                if (tableStyleName == null) //No table style set -> create default PET table style
                {
                    tableStyleName = FallbackTableStyleName;

                    //Check if fallback style exists
                    //If not, create it
                    bool foundFallbackStyle = false;
                    var stylesDefinitions = MainDocPart.StyleDefinitionsPart.Styles;
                    foreach (var curStyle in stylesDefinitions.Elements<Style>())
                    {
                        if (curStyle.StyleId.Equals(FallbackTableStyleName))
                        {
                            foundFallbackStyle = true;
                            break;
                        }
                    }
                    if (!foundFallbackStyle)
                    {
                        Style fallbackTableStyle = new Style(string.Format("<w:style xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:type=\"table\" w:styleId=\"Tabellenraster\"><w:name w:val=\"Table Grid\"/><w:basedOn w:val=\"NormaleTabelle\"/><w:uiPriority w:val=\"59\"/><w:rsid w:val=\"00562ADD\"/><w:pPr><w:spacing w:after=\"0\" w:line=\"240\" w:lineRule=\"auto\"/></w:pPr><w:tblPr><w:tblInd w:w=\"0\" w:type=\"dxa\"/><w:tblBorders><w:top w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:left w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:bottom w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:right w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:insideH w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/><w:insideV w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"auto\"/></w:tblBorders><w:tblCellMar><w:top w:w=\"0\" w:type=\"dxa\"/><w:left w:w=\"108\" w:type=\"dxa\"/><w:bottom w:w=\"0\" w:type=\"dxa\"/><w:right w:w=\"108\" w:type=\"dxa\"/></w:tblCellMar></w:tblPr></w:style>", FallbackTableStyleName));
                        stylesDefinitions.Append(fallbackTableStyle);
                        stylesDefinitions.Save();
                    }
                }
                
                returnElement = new Table();
                var tabProp = new TableProperties() { TableStyle = new TableStyle() { Val = tableStyleName } };
                returnElement.AppendChild(tabProp);

                goto addChildren;
            }
            if (curTemplateElement is TemplateTableRow)
            {
                returnElement = new TableRow();
                goto addChildren;
            }
            if (curTemplateElement is TemplateTableCell)
            {
                returnElement = new TableCell();
                goto addChildren;
            }

            if(curTemplateElement is TemplateList)
            {
                int absNumberingId = Rnd.Next();
                curListId = Rnd.Next();

                //Create new numbering
                foreach (NumberingDefinitionsPart curNumberingPart in MainDocPart.GetPartsOfType<NumberingDefinitionsPart>())
                {
                    //Build numbering source
                    string abstractNumberingSrc = "<w:abstractNum xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:abstractNumId=\"" + absNumberingId + "\">";

                    if(curTemplateElement is TemplateNumberedList)
                        abstractNumberingSrc += TemplateDocumentConfig.NumberedListNumbering;
                    else
                        abstractNumberingSrc += TemplateDocumentConfig.BulletListNumbering;

                    abstractNumberingSrc += "</w:abstractNum>";
                    //----------------------

                    var absNum = new AbstractNum(abstractNumberingSrc);
                    var absNumEnum = curNumberingPart.Numbering.Elements<AbstractNum>().GetEnumerator();
                    while (absNumEnum.MoveNext())
                    { }
                    absNumEnum.Current.InsertAfterSelf(absNum);
                    curNumberingPart.Numbering.AppendChild(new Num(
                                                               new AbstractNumId() {Val = absNumberingId}
                                                               ) { NumberID = curListId });
                    curNumberingPart.Numbering.Save();
                }
                goto addChildren;
            }

            if (curTemplateElement is TemplateImage)
            {
                var image = (TemplateImage)curTemplateElement;
                string imgPath = image.SourceFile;

                ImagePartType imgType = ImagePartType.Jpeg;
                if (imgPath.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
                    imgType = ImagePartType.Png;
                if (imgPath.EndsWith(".gif", StringComparison.OrdinalIgnoreCase))
                    imgType = ImagePartType.Gif;
                if (imgPath.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase))
                    imgType = ImagePartType.Bmp;
                var imagePart = MainDocPart.AddImagePart(imgType);
                using (FileStream imgDataStream = new FileStream(imgPath, FileMode.Open))
                    imagePart.FeedData(imgDataStream);

                var imgFileInfo = new FileInfo(imgPath);
                long widthEMUs = PageContentWidth;
                long heightEMUs = (long)(PageContentWidth * ((double)image.Height / (double)image.Width));


                Paragraph imgParagraph = new Paragraph();
                Run imgRun = new Run();

                RunProperties runProperties = new RunProperties(new NoProof());
                Drawing drawing = new Drawing();

                OxmlDrawingWp.Inline inline = new OxmlDrawingWp.Inline();
                OxmlDrawingWp.Extent extent = new OxmlDrawingWp.Extent() { Cx = widthEMUs, Cy = heightEMUs };
                OxmlDrawingWp.DocProperties docProperties = new OxmlDrawingWp.DocProperties() { Id = (UInt32Value)((uint)Rnd.Next()), Name = imgFileInfo.Name };

                OxmlDrawingWp.NonVisualGraphicFrameDrawingProperties nonVisualGraphicFrameDrawingProperties = new OxmlDrawingWp.NonVisualGraphicFrameDrawingProperties(new OxmlDrawing.GraphicFrameLocks() { NoChangeAspect = true });

                OxmlDrawing.Graphic graphic = new OxmlDrawing.Graphic();
                OxmlDrawing.GraphicData graphicData = new OxmlDrawing.GraphicData() { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" };

                OxmlDrawingPics.Picture picture = new OxmlDrawingPics.Picture();
                OxmlDrawingPics.NonVisualPictureProperties nonVisualPictureProperties = new OxmlDrawingPics.NonVisualPictureProperties(
                    new OpenXmlElement[] { new OxmlDrawingPics.NonVisualDrawingProperties() { Id = (UInt32Value)((uint)Rnd.Next()), Name = imgFileInfo.Name }, new OxmlDrawingPics.NonVisualPictureDrawingProperties() });

                OxmlDrawingPics.BlipFill blipFill = new OxmlDrawingPics.BlipFill(
                    new OpenXmlElement[] {new OxmlDrawing.Blip() { Embed = MainDocPart.GetIdOfPart(imagePart), CompressionState = OxmlDrawing.BlipCompressionValues.Print }, new OxmlDrawing.Stretch(new OxmlDrawing.FillRectangle()) } );

                OxmlDrawingPics.ShapeProperties shapeProperties = new OxmlDrawingPics.ShapeProperties();

                OxmlDrawing.Transform2D transform2D = new OxmlDrawing.Transform2D(
                    new OpenXmlElement[] { new OxmlDrawing.Offset() { X = 0L, Y = 0L }, new OxmlDrawing.Extents() { Cx = widthEMUs, Cy = heightEMUs } } );

                OxmlDrawing.PresetGeometry presetGeometry = new OxmlDrawing.PresetGeometry(new OxmlDrawing.AdjustValueList()) { Preset = OxmlDrawing.ShapeTypeValues.Rectangle };

                shapeProperties.Append(transform2D);
                shapeProperties.Append(presetGeometry);

                picture.Append(nonVisualPictureProperties);
                picture.Append(blipFill);
                picture.Append(shapeProperties);

                graphicData.Append(picture);

                graphic.Append(graphicData);

                inline.Append(extent);
                inline.Append(docProperties);
                inline.Append(nonVisualGraphicFrameDrawingProperties);
                inline.Append(graphic);

                drawing.Append(inline);

                imgRun.Append(runProperties);
                imgRun.Append(drawing);

                imgParagraph.Append(imgRun);

                returnElement = imgParagraph;

                goto addChildren;
            }

            addChildren:

            //Parse child elements
            var childElements = new List<OpenXmlElement>();
            foreach (TemplateElement curChildElement in curTemplateElement.ChildElements)
                childElements.AddRange(ParseTemplateText(curChildElement, returnElement ?? parentElement));

            if (curTemplateElement is TemplateBold)
            {
                foreach (OpenXmlElement curChildElement in childElements)
                    SetRunBold(curChildElement);
                return childElements;
            }

            if (curTemplateElement is TemplateItalic)
            {
                foreach (OpenXmlElement curChildElement in childElements)
                    SetRunItalic(curChildElement);
                return childElements;
            }

            if (curTemplateElement is TemplateTableCell)
            {
                returnElement = new TableCell();
                var oldChildElementsList = childElements;
                childElements = new List<OpenXmlElement>();

                Paragraph curCollectParagraph = null;
                foreach (OpenXmlElement curChildElement in oldChildElementsList)
                {
                    if (curChildElement is Paragraph)
                    {
                        if (curCollectParagraph != null)
                        {
                            childElements.Add(curCollectParagraph);
                            curCollectParagraph = null;
                        }
                        childElements.Add(curChildElement);
                    }
                    else
                    {
                        if (curCollectParagraph == null)
                            curCollectParagraph = new Paragraph();
                        curCollectParagraph.AppendChild(curChildElement);
                    }
                }
                if (curCollectParagraph != null)
                    childElements.Add(curCollectParagraph);

                if(childElements.Count == 0)
                    childElements.Add(new Paragraph());
            }

            if (curTemplateElement is TemplateList)
            {
                var listItems = childElements;
                childElements = new List<OpenXmlElement>();
                foreach (OpenXmlElement curListItem in listItems)
                {
                    var curItemElement = curListItem is Paragraph ? curListItem : new Paragraph(curListItem);
                    CorrectListParagraphs(curItemElement);

                    var numProperties = new NumberingProperties(new NumberingLevelReference() { Val = 0 }, new NumberingId() { Val = curListId });
                    var parProperties = new ParagraphProperties(numProperties);
                    curItemElement.InsertAt(parProperties, 0);

                    childElements.Add(curItemElement);
                }
                returnElement = null;
            }

            if (returnElement == null)
                return childElements.ToArray();

            foreach (OpenXmlElement curChildElement in childElements)
            {
                if (noParagraphChildren && curChildElement is Paragraph)
                {
                    foreach (OpenXmlElement curParagraphElement in curChildElement.ChildElements)
                        (addChildElement ?? returnElement).AppendChild(curParagraphElement.CloneNode(true));
                }
                else
                    (addChildElement ?? returnElement).AppendChild(curChildElement);
            }

            return returnElements ?? new[] {returnElement};
        }

        private void CorrectListParagraphs(OpenXmlElement listItem)
        {
            if (listItem is Paragraph)
            {
                foreach (OpenXmlElement curParagraphChild in listItem.ChildElements)
                {
                    if (curParagraphChild is ParagraphProperties)
                    {
                        ParagraphProperties parProp = (ParagraphProperties) curParagraphChild;
                        //Found paragraph, that needs to be corrected
                        NumberingProperties numProp = (parProp).NumberingProperties;
                        numProp.NumberingLevelReference = new NumberingLevelReference() {Val = numProp.NumberingLevelReference.Val + 1};
                        break;
                    }
                }
            }
            foreach (OpenXmlElement curChildElement in listItem.ChildElements)
                CorrectListParagraphs(curChildElement);
        }

        private static void SetRunBold(OpenXmlElement curElement)
        {
            if(curElement is Run)
            {
                var runProperties = new RunProperties {Bold = new Bold()};
                curElement.InsertAt(runProperties, 0);
            }
            else
            {
                foreach (OpenXmlElement curChildElement in curElement.ChildElements)
                    SetRunBold(curChildElement);
            }
        }

        private static void SetRunItalic(OpenXmlElement curElement)
        {
            if (curElement is Run)
            {
                var runProperties = new RunProperties { Italic = new Italic() };
                curElement.InsertAt(runProperties, 0);
            }
            else
            {
                foreach (OpenXmlElement curChildElement in curElement.ChildElements)
                    SetRunItalic(curChildElement);
            }
        }


        /// <summary>
        /// Gives all elements, that still have equal content and tag, empty content.
        /// </summary>
        public void CleanUpElements()
        {
            foreach (OpenXmlElement openXmlElement in RootElements)
            {
                CleanUpElements(openXmlElement);
            }
        }

        private static void CleanUpElements(OpenXmlElement root)
        {
            foreach (OpenXmlElement openXmlElement in root.ChildElements)
            {
                if (openXmlElement is Text)
                {
                    foreach (OpenXmlAttribute curAttribute in openXmlElement.ExtendedAttributes)
                    {
                        if (curAttribute.LocalName.Equals(TagAttributeName))
                        {
                            if (curAttribute.Value.Equals(((Text)openXmlElement).Text))
                            {
                                ((Text) openXmlElement).Text = string.Empty;
                                break;
                            }
                        }
                    }
                }
                CleanUpElements(openXmlElement);
            }
        }

        public OpenXmlElement[] CloneSection(string beginElementName, string endElementName)
        {
            //Find begin/end element
            var beginElement = Section_GetUniqueElement(beginElementName);
            var endElement = Section_GetUniqueElement(endElementName);

            //Find common parent element
            OpenXmlElement directChildBegin;
            OpenXmlElement directChildEnd;
            OpenXmlElement commonParent = Section_FindCommonParentElement(beginElement, endElement, out directChildBegin, out directChildEnd);

            //Copy elements in between
            List<OpenXmlElement> clonedElementsList = new List<OpenXmlElement>();
            OpenXmlElement curCopiedElement = directChildBegin.NextSibling();
            while(!curCopiedElement.Equals(directChildEnd))
            {
                var curElementClone = curCopiedElement.CloneNode(true);
                clonedElementsList.Add(curElementClone);
                commonParent.InsertBefore(curElementClone, directChildBegin);

                curCopiedElement = curCopiedElement.NextSibling();
            }

            return clonedElementsList.ToArray();
        }

        private OpenXmlElement Section_GetUniqueElement(string elementName)
        {
            var elementsList = new List<Text>();
            foreach (OpenXmlElement curRoot in RootElements)
                GetElementsByTag(curRoot, elementName, ref elementsList);
            if (elementsList.Count == 0) //Section not found
                throw new SectionNotFoundException(elementName);
            if (elementsList.Count > 1) //Section to be cloned must be unique
                throw new SectionNameNotUniqueException(elementName);
            return elementsList[0];
        }

        /// <summary>
        /// Finds the least top-level ancester element, that both begin and end element share.
        /// </summary>
        /// <param name="directChildBegin">Child of commonParent, that is the parent of the begin element.</param>
        /// <returns></returns>
        private OpenXmlElement Section_FindCommonParentElement(OpenXmlElement beginElement, OpenXmlElement endElement, out OpenXmlElement directChildBegin, out OpenXmlElement directChildEnd)
        {
            OpenXmlElement commonParent = null;
            directChildBegin = null;
            directChildEnd = null;
            OpenXmlElement curBeginParent = beginElement.Parent;
            while (curBeginParent != null)
            {
                directChildEnd = null;
                OpenXmlElement curEndParent = endElement.Parent;
                while (curEndParent != null)
                {
                    if (curBeginParent.Equals(curEndParent))
                    {
                        commonParent = curBeginParent;
                        break;
                    }
                    directChildEnd = curEndParent;
                    curEndParent = curEndParent.Parent;
                }
                if (commonParent != null)
                    break;

                directChildBegin = curBeginParent;
                curBeginParent = curBeginParent.Parent;
            }
            if (commonParent == null || directChildBegin == null)
                throw new NoCommonParentException(beginElement.InnerText, endElement.InnerText);

            return commonParent;
        }

        public void RemoveSection(string beginElementName, string endElementName)
        {
            ///TODO: Don't delete previous elements on the same level as the beginning element  (e.g. prev. elements on the same line)

            //Find begin/end element
            var beginElement = Section_GetUniqueElement(beginElementName);
            var endElement = Section_GetUniqueElement(endElementName);

            //Find common parent element
            OpenXmlElement directChildBegin;
            OpenXmlElement directChildEnd;
            var commonParent = Section_FindCommonParentElement(beginElement, endElement, out directChildBegin, out directChildEnd);

            OpenXmlElement stopDeleteElement = directChildEnd.NextSibling();
            OpenXmlElement curDeletedElement = directChildBegin;
            do
            {
                OpenXmlElement nextDeleteElement = curDeletedElement.NextSibling();
                curDeletedElement.Remove();
                curDeletedElement = nextDeleteElement;

            } while (curDeletedElement != null && !curDeletedElement.Equals(stopDeleteElement));

            #region Check if commonParent is a now empty table cell. If so, insert an empty paragraph
            if (commonParent is TableCell)
            {
                bool foundParagraph = false;
                foreach (OpenXmlElement curCellChild in commonParent.ChildElements)
                {
                    if (curCellChild is Paragraph)
                    {
                        foundParagraph = true;
                        break;
                    }
                }
                if (!foundParagraph)
                {
                    var emptyParagraph = new Paragraph();
                    var emptyRun = new Run();
                    emptyRun.InsertAt(new Text(string.Empty), 0);
                    emptyParagraph.InsertAt(emptyRun, 0);
                    commonParent.InsertAfter(emptyParagraph, commonParent.FirstChild);
                }
            } 
            #endregion
        }

        //public void RemoveElements(string elementName)
        //{
        //    var elementsList = new List<Text>();
        //    foreach (OpenXmlElement curRoot in RootElements)
        //        GetElementsByTag(curRoot, elementName, ref elementsList);

        //    foreach (Text curElement in elementsList)
        //        curElement.Remove();
        //}

        ///// <summary>
        ///// Selects the XmlNode the by TID in an entire document.xml file.
        ///// </summary>
        ///// <param name="TID">TID.</param>
        ///// <returns>XmlNode T</returns>
        //public XmlNode SelectByTID(string TID)
        //{
        //    return this.DocumentXml.SelectSingleNode(string.Format(Queries.GetTextByTid, TID), this.Nsmng);
        //}

        ///// <summary>
        ///// Selects the XmlNode the by TID in an entire document.xml file.
        ///// It is possible to specify which one of the T-Nodes has to be selected.
        ///// If there are more than 1 T-Nodes available than this method choses the one in row number "count"
        ///// </summary>
        ///// <param name="TID">The TID.</param>
        ///// <param name="count">count specifying the row</param>
        ///// <returns>XmlNode T</returns>
        //public XmlNode SelectByTID(string TID, int count)
        //{
        //    TID = string.Format("{0}{1}", TID, count.ToString());
        //    return this.DocumentXml.SelectSingleNode(string.Format(Queries.GetTextByTid, TID), this.Nsmng);
        //}

        ///// <summary>
        ///// Selects the by TID.
        ///// </summary>
        ///// <param name="TID">The TID.</param>
        ///// <returns>XmlNodeList containing the XmlNode elements with the given TID</returns>
        //public XmlNodeList SelectByTIDs(string TID)
        //{
        //    return this.DocumentXml.SelectNodes(string.Format(Queries.GetTextByTid, TID), this.Nsmng);
        //}

        ///// <summary>
        ///// Selects the XmlNode the by TID in an entire document.xml file.
        ///// It is possible to specify which ones of the T-Nodes has to be selected.
        ///// If there are more than 1 T-Nodes available than this method choses the ones in row number "count"
        ///// </summary>
        ///// <param name="TID">The TID.</param>
        ///// <param name="count">count specifying the row</param>
        ///// <returns>XmlNodeList of Ts</returns>
        //public XmlNodeList SelectByTIDs(string TID, int count)
        //{
        //    TID = string.Format("{0}{1}", TID, count.ToString());
        //    return this.DocumentXml.SelectNodes(string.Format(Queries.GetTextByTid, TID), this.Nsmng);
        //}

        ///// <summary>
        ///// Given a TID, a list of text values this method gets the T-Nodes via TID, 
        ///// copies them with their whole paragraphs sets the Text from the value list.
        ///// It is possible to give indexes to the newly produced T-Nodes for addresing them afterwards. This index can be 
        ///// static or a up-counting integer. If overwrite then the copied T-Node is overwritten by the first T-Nodes textvalue
        ///// </summary>
        ///// <param name="TID">TID</param>
        ///// <param name="valuesList">list of the text values</param>
        ///// <param name="overWrite">if set to <c>true</c> T-Node containing the placeholder gets overwritten</param>
        ///// <param name="bIndex">if set to <c>true</c> an index is set to TIDs of the new T-Nodes for addresing purposes</param>
        ///// <param name="staticIndex">integer of a static Value</param>
        //public void CloneAndEvaluatePs(string TID, string[] values, bool overWrite, bool bIndex, int staticIndex)
        //{
        //    XmlNode refT = this.SelectByTID(TID);
        //    if (refT == null)
        //        return;
        //    XmlNode refP = PofT(refT);
        //    if (values != null)
        //    {
        //        for (int i = values.Length - 1; i >= 0; i--)
        //        {
        //            if (i == 0 && overWrite)
        //            {
        //                SetTextByCustomTID(refT, TID, values[0]);
        //            }
        //            else
        //            {
        //                XmlNode clone = refP.Clone();
        //                SetTextByCustomTID(clone, TID, values[i]);
        //                refP.ParentNode.InsertAfter(clone, refP);
        //            }
        //        }
        //        EvaluatePs(TID, bIndex, staticIndex);
        //    }
        //    else
        //    {
        //        refP.ParentNode.RemoveChild(refP);
        //    }
        //}

        ///// <summary>
        ///// See other overload. Except that this version takes only one value and inserts it a given number of times.
        ///// </summary>
        //public void CloneAndEvaluatePs(string TID, string values, int count, bool overWrite, bool bIndex, int staticIndex)
        //{
        //    XmlNode refT = this.SelectByTID(TID);
        //    if (refT == null)
        //        return;

        //    XmlNode refP = PofT(refT);
        //    if (values != null)
        //    {
        //        for (int i = count - 1; i >= 0; i--)
        //        {
        //            if (i == 0 && overWrite)
        //            {
        //                SetTextByCustomTID(refT, TID, values);
        //            }
        //            else
        //            {
        //                XmlNode clone = refP.Clone();
        //                SetTextByCustomTID(clone, TID, values);
        //                refP.ParentNode.InsertAfter(clone, refP);
        //            }
        //        }
        //        EvaluatePs(TID, bIndex, staticIndex);
        //    }
        //    else
        //    {
        //        refP.ParentNode.RemoveChild(refP);
        //    }
        //}

        //private void EvaluatePs(string TID, bool bIndex, int staticIndex)
        //{
        //    XmlNodeList nodesTobeChanged = SelectByTIDs(TID);
        //    int j = 0;
        //    foreach (XmlNode node in nodesTobeChanged)
        //    {
        //        if (bIndex)
        //        {
        //            if (j == 0)
        //            {
        //                j++;
        //            }
        //            else
        //            {
        //                ModifyTIDofT(node, j.ToString());
        //                j++;
        //            }
        //        }
        //        else
        //        {
        //            if (j == 0)
        //            {
        //                j++;
        //            }
        //            else
        //            {
        //                ModifyTIDofT(node, staticIndex.ToString());
        //            }
        //        }
        //    }
        //}

        ///// <summary>
        ///// Given a TID, a CDATA text value this method gets the T-Nodes via TID, 
        ///// copies them with their whole paragraphs and sets the Text from the value list.
        ///// It is possible to give indexes to the newly produced T-Nodes for addresing them later on. This index can be 
        ///// static or a up-counting integer. If the parameter overwrite set true then the copied T-Node is overwritten by the first T-Nodes textvalue
        ///// </summary>
        ///// <param name="TID">TID</param>
        ///// <param name="valuesList">list of the text values</param>
        ///// <param name="overWrite">if set to <c>true</c> T-Node containing the placeholder gets overwritten</param>
        ///// <param name="bIndex">if set to <c>true</c> an index is set to TIDs of the new T-Nodes for addresing purposes</param>
        ///// <param name="staticIndex">integer of a static Value</param>
        //public void CloneAndEvaluateCDATA(string TID, string CDATAText, bool overWrite, int staticIndex)
        //{
        //    List<string> CDATAPartValues = new List<string>();

        //    if (string.IsNullOrEmpty(CDATAText))
        //    {
        //        CDATAPartValues.Add(string.Empty);
        //    }
        //    else
        //    {
        //        XmlDocument textModuleXML = new XmlDocument();
        //        XmlNode docNode = textModuleXML.CreateXmlDeclaration("1.0", "ISO-8859-1", null);
        //        textModuleXML.AppendChild(docNode);

        //        XmlNode paragrapsNode = textModuleXML.CreateElement("paragraphs");

        //        paragrapsNode.InnerXml = CDATAText;
        //        textModuleXML.AppendChild(paragrapsNode);

        //        XmlNodeList nodes = textModuleXML.SelectNodes("//p");
        //        if (nodes.Count == 0)
        //        {
        //            CDATAPartValues.Add(CDATAText);
        //        }
        //        else
        //        {
        //            foreach (XmlNode node in nodes)
        //            {
        //                CDATAPartValues.Add(node.InnerText);
        //            }
        //        }
        //        CloneAndEvaluatePs(TID, CDATAPartValues.ToArray(), false, false, staticIndex);
        //    }
        //}

        ///// <summary>
        ///// Moves one or more P nodes from source a position to destination position
        ///// given trough a destination P nodes list.
        ///// </summary>
        ///// <param name="sources">source Ps to be moved</param>
        ///// <param name="dest">destination Ps to be moved after</param>
        //public void MoveParagraph(XmlNodeList sources, XmlNodeList dest)
        //{
        //    XmlNode destT = dest[dest.Count - 1];
        //    if (destT == null)
        //        return;

        //    XmlNode destP = PofT(destT);
        //    for (int i = sources.Count - 1; i >= 0; i--)
        //    {
        //        XmlNode sourceP = PofT(sources[i]);
        //        XmlNode clone = sourceP.Clone();
        //        sourceP.ParentNode.RemoveChild(sourceP);
        //        destP.ParentNode.InsertAfter(clone, destP);
        //    }

        //}

        ///// <summary>
        ///// given a t node this method returns the P node
        ///// </summary>
        ///// <param name="tNode">tnode.</param>
        ///// <returns></returns>
        //public static XmlNode PofT(XmlNode tNode)
        //{
        //    if (tNode.ParentNode.ParentNode != null)
        //    {
        //        return tNode.ParentNode.ParentNode;
        //    }
        //    return tNode;
        //}

        ///// <summary>
        ///// Removes a P-node via its TID
        ///// </summary>
        ///// <param name="TID">TID</param>
        //public void RemovePbyTID(string TID)
        //{
        //    XmlNode node = SelectByTID(TID);
        //    if (node != null)
        //    {
        //        PofT(node).ParentNode.RemoveChild(PofT(node));
        //        return;
        //    }
        //    else
        //    {
        //        return;
        //    }
        //}

        ///// <summary>
        ///// Modifies the TID of a T node.
        ///// </summary>
        ///// <param name="T">T node</param>
        ///// <param name="appendedStr">string to be appended to the TID value</param>
        //private void ModifyTIDofT(XmlNode T, string appendedStr)
        //{
        //    if (T == null)
        //        return;

        //    XmlAttribute attr = T.Attributes[0];
        //    if (attr != null)
        //    {
        //        T.Attributes[0].Value = string.Format("{0}{1}", T.Attributes[0].Value, appendedStr);
        //    }
        //}

    }


    public class CloneSectionException : Exception
    {
        public CloneSectionException()
        {
        }

        public CloneSectionException(string message)
            : base(message)
        {
        }
    }

    public class SectionNameNotUniqueException : CloneSectionException
    {
        public SectionNameNotUniqueException()
        {
        }

        public SectionNameNotUniqueException(string sectionName)
            : base("Section '" + sectionName + "' is not unique.")
        {
        }
    }

    public class SectionNotFoundException : CloneSectionException
    {
        public SectionNotFoundException()
        {
        }

        public SectionNotFoundException(string sectionName)
            : base("Section '" + sectionName + "' has not been found.")
        {
        }
    }

    public class NoCommonParentException : CloneSectionException
    {
        public NoCommonParentException()
        {
        }

        public NoCommonParentException(string beginSectionName, string endSectionName)
            : base("Sections '" + beginSectionName + "' and '" + endSectionName + "' have no common parent.")
        {
        }
    }
}
